interface Msg2Component {
    componentId: string, // 组件id, 应用内不可重复
    content: any // 消息内容
}

interface ComponentSchema {
    id: string
    elementId: string // 可拖拽的组件元素id
    dragState: DragState // 拖拽状态
}

/**
 * 拖拽状态
 */
enum DragState {
    BeforeDrag = 'beforeDrag', // 未被拖出
    AfterDrag = 'afterDrag' // 已被拖出
}

/**
 * 消息类型
 */
enum MessageType {
    DragEnd = 'dragEnd',
    GetConfig = 'getConfig',
    DeleteDraged = 'deleteDraged',
    DragStateChange = 'dragStateChange',
}

const config = JSON.parse(localStorage.getItem('dragMsgConfig'));
const framePath: string = config.framePath;
const appId: string = config.id;
let components: Array<ComponentSchema>;

const initConfig = {
    init() {
        window.parent.postMessage({
            type: MessageType.GetConfig,
            appId,
        }, framePath);
    }
};

const dragMsg = {
    componentIdList: null,
    /**
     * 初始化插件
     * @param componentIdList {Array<string>} 当前页面需要拖拽的组件id列表
     */
    init(componentIdList: Array<string>): void {
        frameMsg.init();
        initConfig.init();
        this.componentIdList = componentIdList;
    },
    /**
     * 应用发送消息给组件
     * @param componentId {string} 组件元素id
     * @param content 消息内容
     */
    sendMsg2Component({componentId: elementId, content}: Msg2Component): void {
        const componentDragState = tools.$(elementId).getAttribute('data-drag-state');
        // 根据组件状态判断是发送给自己还是拖拽到其他窗口的组件
        if (componentDragState === DragState.AfterDrag) {
            // console.log(componentId, '已拖出');
            window.parent.postMessage({
                type: 'app2DrapedComponent',
                id: components.find(value => value.elementId === elementId)!.id,
                content
            }, framePath);
        }
        if (componentDragState === DragState.BeforeDrag) {
            // console.log(componentId, '未拖出');
            const iframe = tools.$(elementId + 'iframe') as HTMLIFrameElement;
            iframe.contentWindow.postMessage(content, '*');
        }
    }
};

const tools = {
    $(id: string): HTMLElement | null {
        return document.getElementById(id);
    },
    on(el: Element, type: string, listener: EventListenerOrEventListenerObject) {
        el.addEventListener(type, listener);
    },
    addClass(el: Element, className: string) {
        el.classList.add(className);
    },
    removeClass(el: Element, cls: string[]) {
        if (arguments.length < 2) {
            el.classList.remove(cls[0]);
        } else {
            for (let i = 1, len = arguments.length; i < len; i++) {
                el.classList.remove(arguments[i]);
            }
        }
    }
};

const drag = {
    initDragItem(elementId: string): void {
        const comp = components.find(component => component.elementId === elementId);
        // 确保组件有配置到权限系统，才对元素进行初始化拖拽和通信
        if (comp === undefined) return;

        let el = tools.$(elementId);
        el.setAttribute('draggable', 'true');
        // 添加拖拽区域的iframe的id值为组件id+'iframe'
        let iframeNode = el.getElementsByTagName('iframe')[0];
        if (iframeNode === undefined) {
            throw new Error('拖拽区域内未检测到iframe节点，将导致消息发送功能无法正常使用');
        } else {
            iframeNode.setAttribute('id', elementId + 'iframe');
        }
        tools.on(el, 'dragstart', this.handleDragStart);
        tools.on(el, 'dragend', this.handleDragEnd);

        if (comp.dragState === DragState.BeforeDrag) {
            el.setAttribute('data-drag-state', DragState.BeforeDrag);
        } else {
            el.setAttribute('data-drag-state', DragState.AfterDrag);
            el.hidden = true;
        }

    },
    handleDragStart(ev: DragEvent): void {
        const el = ev.target as Element;
        tools.addClass(el, 'dragging');
        const elementId = el.getAttribute('id');
        const component = components.find(c => c.elementId === elementId);
        ev.dataTransfer.setData('text', JSON.stringify({
            componentId: component.id,
            from: 'app'
        }));
    },
    handleDragEnd(ev: DragEvent): boolean {
        const el = ev.target as Element;
        tools.removeClass(el, ['dragging']);
        ev.preventDefault();
        if (ev.stopPropagation) {
            ev.stopPropagation();
        }
        return false;
    },
    init(componentIdList: Array<string>): void {
        componentIdList.forEach(componentId => {
            this.initDragItem(componentId);
        });
    },
};

const frameMsg = {
    receiveParentMsg(ev: MessageEvent): void {
        // 接收父级消息，如果类型是getConfig，则设置app配置,然后初始化拖拽
        if (ev.data.type === MessageType.GetConfig) {
            // console.log('GetConfig');
            components = ev.data.components;
            drag.init(dragMsg.componentIdList);
        }
        // 接收父级消息，如果类型是dragEnd， 则隐藏组件
        if (ev.data.type === MessageType.DragEnd) {
            let el = tools.$(ev.data.elementId);
            el.hidden = true;
            el.setAttribute('data-drag-state', DragState.AfterDrag);
        }
        // 接收父级消息，如果类型是DeleteDraged，则显示组件
        if (ev.data.type === MessageType.DeleteDraged) {
            let el = tools.$(ev.data.elementId);
            el.hidden = false;
            el.setAttribute('data-drag-state', DragState.BeforeDrag);
        }
        // 组件状态拖拽状态批量改变
        if (ev.data.type === MessageType.DragStateChange) {
            ev.data.components.forEach(v => {
                const el = tools.$(v.elementId);
                el.hidden = v.dragState === DragState.AfterDrag;
                el.setAttribute('data-drag-state', v.dragState);
            });
        }
    },
    init(): void {
        window.addEventListener('message', this.receiveParentMsg);
    },
};
